package com.sks.web.action;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.cglib.beans.BeanCopier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.actions.BaseAction;
import org.apache.struts.util.MessageResources;

import com.sks.bean.privilege.Employee;
import com.sks.core.SystemConstants;
import com.sks.service.privilege.SystemPrivilegeService;
import com.sks.utils.WebAppContextUtil;
import com.sks.utils.WebUtil;
import com.sks.web.action.privilege.Permission;

public abstract class BasicAction extends BaseAction {

	/**
	 * Commons Logging instance.
	 */
	protected static Log log = LogFactory.getLog(BasicAction.class);

	protected String LIST = "list";

	protected String ADDUI = "addUI";

	protected String EDITUI = "editUI";

	protected String ADD = "add";

	protected String EDIT = "edit";

	protected String MESSAGE = "message";

	protected String QUERY = "query";

	protected String BEFORE = "before";

	protected String ERROR = "error";
	
	protected String ACTION = "action";
	
	protected String PAGEVIEW = "pageView";
	
	protected String INFO = "info";
	
	private String errorForwardName;
	
	protected static MessageResources resource = null;
	
	protected void copyProperties(Object source, Object target) {
		BeanCopier copy = BeanCopier.create((Class) source.getClass(),
				(Class) target.getClass(), false);
		copy.copy(source, target, null);
	}

	// ----------------------------------------------------- Instance Variables

	/**
	 * The Class instance of this <code>BasicAction</code> class.
	 */
	protected Class clazz = this.getClass();

	/**
	 * The set of Method objects we have introspected for this class, keyed by
	 * method name. This collection is populated as different methods are
	 * called, so that introspection needs to occur only once per method name.
	 */
	protected HashMap methods = new HashMap();

	/**
	 * The set of argument type classes for the reflected method call. These are
	 * the same for all calls, so calculate them only once.
	 */
	protected Class[] types = { ActionMapping.class, ActionForm.class,
			HttpServletRequest.class, HttpServletResponse.class };

	// --------------------------------------------------------- Public Methods

	/**
	 * Process the specified HTTP request, and create the corresponding HTTP
	 * response (or forward to another web component that will create it).
	 * Return an <code>ActionForward</code> instance describing where and how
	 * control should be forwarded, or <code>null</code> if the response has
	 * already been completed.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The HTTP request we are processing
	 * @param response
	 *            The HTTP response we are creating
	 * @return The forward to which control should be transferred, or
	 *         <code>null</code> if the response has been completed.
	 * @throws Exception
	 *             if an exception occurs
	 */
	public ActionForward execute(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		if (isCancelled(request)) {
			ActionForward af = cancelled(mapping, form, request, response);

			if (af != null) {
				return af;
			}
		}

		// Get the parameter. This could be overridden in subclasses.
		String parameter = getParameter(mapping, form, request, response);

		// Get the method's name. This could be overridden in subclasses.
		String methodName = getMethodName(mapping, form, request, response,
				parameter);

		// Prevent recursive calls
		if ("execute".equals(methodName) || "perform".equals(methodName)) {
			String message = messages.getMessage("dispatch.recursive", mapping
					.getPath());

			log.error(message);
			throw new ServletException(message);
		}
		
		if (null == resource) {
			resource = getResources(request);
		}
		
		// set the default encoding utf-8
		request.setCharacterEncoding(SystemConstants.DEFAULT_ENCODING);
		response.setCharacterEncoding(SystemConstants.DEFAULT_ENCODING);

		// Invoke the named method, and return the result
		return dispatchMethod(mapping, form, request, response, methodName);
	}

	/**
	 * Method which is dispatched to when there is no value for specified
	 * request parameter included in the request. Subclasses of
	 * <code>DispatchAction</code> should override this method if they wish to
	 * provide default behavior different than throwing a ServletException.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The non-HTTP request we are processing
	 * @param response
	 *            The non-HTTP response we are creating
	 * @return The forward to which control should be transferred, or
	 *         <code>null</code> if the response has been completed.
	 * @throws Exception
	 *             if the application business logic throws an exception.
	 */
	protected ActionForward unspecified(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		String message = messages.getMessage("dispatch.parameter", mapping
				.getPath(), mapping.getParameter());

		log.error(message);

		throw new ServletException(message);
	}

	/**
	 * Method which is dispatched to when the request is a cancel button submit.
	 * Subclasses of <code>DispatchAction</code> should override this method if
	 * they wish to provide default behavior different than returning null.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The non-HTTP request we are processing
	 * @param response
	 *            The non-HTTP response we are creating
	 * @return The forward to which control should be transferred, or
	 *         <code>null</code> if the response has been completed.
	 * @throws Exception
	 *             if the application business logic throws an exception.
	 * @since Struts 1.2.0
	 */
	protected ActionForward cancelled(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {
		return null;
	}

	// ----------------------------------------------------- Protected Methods

	/**
	 * Dispatch to the specified method.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The non-HTTP request we are processing
	 * @param response
	 *            The non-HTTP response we are creating
	 * @param methodName
	 *            The name of the method to invoke
	 * @return The forward to which control should be transferred, or
	 *         <code>null</code> if the response has been completed.
	 * @throws Exception
	 *             if the application business logic throws an exception.
	 * @since Struts 1.1
	 */
	protected ActionForward dispatchMethod(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response, String methodName) throws Exception {
		// Make sure we have a valid method name to call.
		// This may be null if the user hacks the query string.
		if (methodName == null) {
			return this.unspecified(mapping, form, request, response);
		}

		// Identify the method object to be dispatched to
		Method method = null;
		Object[] args = { mapping, form, request, response };

		// validate in server
		ActionForward afterForward = validateInAction(mapping, form, request, response, methodName);
		if (null != afterForward) {
			return afterForward;
		}

		// 防重复提交，默认在add和edit方法里面使用令牌
		ActionForward tokenForward = validateToken(mapping, form, request, response, methodName);
		if (null != tokenForward) {
			return tokenForward;
		}

		try {
			method = getMethod(methodName);
		} catch (NoSuchMethodException e) {
			String message = messages.getMessage("dispatch.method", mapping
					.getPath(), methodName);

			log.error(message, e);

			String userMsg = messages.getMessage("dispatch.method.user",
					mapping.getPath());
			throw new NoSuchMethodException(userMsg);
		}

		ActionForward forward = null;

		try {
			forward = (ActionForward) method.invoke(this, args);
		} catch (ClassCastException e) {
			String message = messages.getMessage("dispatch.return", mapping
					.getPath(), methodName);

			log.error(message, e);
			throw e;
		} catch (IllegalAccessException e) {
			String message = messages.getMessage("dispatch.error", mapping
					.getPath(), methodName);

			log.error(message, e);
			throw e;
		} catch (InvocationTargetException e) {
			// Rethrow the target exception if possible so that the
			// exception handling machinery can deal with it
			Throwable t = e.getTargetException();

			if (t instanceof Exception) {
				throw ((Exception) t);
			} else {
				String message = messages.getMessage("dispatch.error", mapping
						.getPath(), methodName);

				log.error(message, e);
				throw new ServletException(t);
			}
		} catch (Exception e) {
			log.error("error:", e);
			throw e;
		}

		// Return the returned ActionForward instance
		return (forward);
	}

	/**
	 * <p>
	 * Returns the parameter value.
	 * </p>
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The HTTP request we are processing
	 * @param response
	 *            The HTTP response we are creating
	 * @return The <code>ActionMapping</code> parameter's value
	 * @throws Exception
	 *             if the parameter is missing.
	 */
	protected String getParameter(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		// Identify the request parameter containing the method name
		String parameter = mapping.getParameter();

		if (parameter == null) {
			String message = messages.getMessage("dispatch.handler", mapping
					.getPath());

			log.error(message);

			throw new ServletException(message);
		}

		return parameter;
	}

	/**
	 * Introspect the current class to identify a method of the specified name
	 * that accepts the same parameter types as the <code>execute</code> method
	 * does.
	 * 
	 * @param name
	 *            Name of the method to be introspected
	 * @return The method with the specified name.
	 * @throws NoSuchMethodException
	 *             if no such method can be found
	 */
	protected Method getMethod(String name) throws NoSuchMethodException {
		synchronized (methods) {
			Method method = (Method) methods.get(name);

			if (method == null) {
				method = clazz.getMethod(name, types);
				methods.put(name, method);
			}

			return (method);
		}
	}

	/**
	 * Returns the method name, given a parameter's value.
	 * 
	 * @param mapping
	 *            The ActionMapping used to select this instance
	 * @param form
	 *            The optional ActionForm bean for this request (if any)
	 * @param request
	 *            The HTTP request we are processing
	 * @param response
	 *            The HTTP response we are creating
	 * @param parameter
	 *            The <code>ActionMapping</code> parameter's name
	 * @return The method's name.
	 * @throws Exception
	 *             if an error occurs.
	 * @since Struts 1.2.0
	 */
	protected String getMethodName(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response,
			String parameter) throws Exception {
		// Identify the method name to be dispatched to.
		// dispatchMethod() will call unspecified() if name is null
		return request.getParameter(parameter);
	}

	/**
	 * 防重复提交验证，默认对add和edit方法进行校验，当表单重复提交时，返回MESSAGE页面。可重写此方法进行重复提交校验。
	 * */
	protected ActionForward validateToken(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response, String methodName) throws Exception {
		if (ADD.equals(methodName) || EDIT.equals(methodName)) {
			ActionMessages errors = new ActionMessages();
			if (!isTokenValid(request)) {
				// 如果两个值相等,即表单重复提交
				this.saveToken(request);
				errors.add("", new ActionMessage("errors.submit.repeat"));
				saveErrors(request, errors);
				// 跳转至edit页面
				return mapping.findForward(MESSAGE);
			} else { // 当用户首次提交时返!isTokenValid()返回true
				this.resetToken(request); // 将用户session中的token清空
			}
		}
		return null;
	}
	
	/**
	 * 服务端验证，默认验证before+method命名的方法，并且在有错误的时候返回edit页面。可重写此方法进行服务端校验。
	 * */
	protected ActionForward validateInAction(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response, String methodName) throws Exception {
		try {
			String beforeName = BEFORE + methodName;
			Method beforeMethod = getMethod(beforeName);
			if (null == beforeMethod) {
				throw new NoSuchMethodException(beforeName);
			}
			beforeMethod.invoke(this, new Object[]{ mapping, form, request, response });
			if (hasErrors(request)) {
				if (null == errorForwardName && !"".equals(errorForwardName)) {
					return mapping.findForward(EDIT);
				} else {
					return mapping.findForward(errorForwardName);
				}
			}
		} catch (Exception e) {
		}
		return null;
	}
	
	protected void setReadOnly(HttpServletRequest request, String methodName) throws Exception {
		boolean isReadOnly = false;
		Method method = getMethod(methodName);
		if (null != method) {
			Permission permission = method.getAnnotation(Permission.class);
			if (null != permission) {
				Employee emp = WebUtil.getEmployee(request);
				if (null != permission && null != emp && "view".equals(permission.privilege())) {
					SystemPrivilegeService service = (SystemPrivilegeService) WebAppContextUtil.getBean("systemPrivilegeServiceBean");
					isReadOnly = !service.hasPrivilege(emp.getUsername(), permission.module(), "update");
				}
			}
		}
		request.setAttribute(SystemConstants.SYS_IS_READ_ONLY, isReadOnly);
	}
	
	protected String getRemoteAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}
	
	protected boolean hasErrors(HttpServletRequest request) {
		return !this.getErrors(request).isEmpty();
	}

	protected String getErrorForwardName() {
		return errorForwardName;
	}

	protected void setErrorForwardName(String errorForwardName) {
		this.errorForwardName = errorForwardName;
	}
	
	protected String getMessage(String key) {
		return resource.getMessage(key);
	}
	
	protected String getMessage(String key, Object arg0) {
		return resource.getMessage(key, arg0);
	}
	
	protected String getMessage(String key, Object[] args) {
		return resource.getMessage(key, args);
	}
	
	protected String getMessage(String key, Object arg0, Object arg1) {
		return resource.getMessage(key, arg0, arg1);
	}
	
	protected String getMessage(String key, Object arg0, Object arg1, Object arg2) {
		return resource.getMessage(key, arg0, arg1, arg2);
	}
	
	protected String getMessage(String key, Object arg0, Object arg1, Object arg2, Object arg3) {
		return resource.getMessage(key, arg0, arg1, arg2, arg3);
	}
}
